/*
 * Ganesa Copyright (C) 2008 Wim Crols <twimpiex@gnx.be>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 3
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package be.gnx.ganesa.application.main;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import org.eclipse.swt.widgets.Display;

public final class JarLoader {

  private static String osName = System.getProperty("os.name");
  private static String osArch = System.getProperty("os.arch");
  private static String osVersion = System.getProperty("os.version");
  private static String fileSeparator = System.getProperty("file.separator");
  private static String pathSeparator = System.getProperty("path.separator");
  private static String lineSeparator = System.getProperty("line.separator");

  public JarLoader() {
    if (!isSWTAvailable()) {
      try {
        ProtectionDomain protectionDomain = getClass().getProtectionDomain();
        CodeSource codeSource = protectionDomain.getCodeSource();
        URL codeSourceUrl = codeSource.getLocation();
        String codeSourceFile = URLDecoder.decode(codeSourceUrl.getFile(), "UTF-8");
        File jarFile = new File(codeSourceFile).getAbsoluteFile();
        System.out.println(codeSourceFile);
        File classPathFile = createTemporaryClassPathDirectory();
        load(jarFile, classPathFile);
      } catch (ZipException zipException) {
        System.out.println("Error while loading SWT. Traceback: ");
        zipException.printStackTrace();
        System.exit(1);
      } catch (IOException ioException) {
        System.out.println("Error while loading SWT. Traceback: ");
        ioException.printStackTrace();
        System.exit(1);
      } catch (IllegalStateException illegalStateException) {
        System.out.println("Error while loading SWT. Traceback: ");
        illegalStateException.printStackTrace();
        System.exit(1);
      }
    }
  }

  private void load(File jarFile, File classPathFile) throws IOException {
    extract(jarFile, classPathFile);
    addFileToClassLoader(classPathFile.getAbsolutePath() + fileSeparator + "swt.jar");
  }

  private File createTemporaryClassPathDirectory() throws IOException {
    File tempFile = File.createTempFile("ganesaClassPath", "");
    if (!tempFile.delete()) {
      throw new IOException();
    }
    if (!tempFile.mkdir()) {
      throw new IOException();
    }
    return tempFile.getAbsoluteFile();
  }

  private static void extract(File jarFile, File classPathFile) throws ZipException, IOException, IllegalStateException {
    System.err.println("os.name = " + osName);
    System.err.println("os.arch = " + osArch);
    System.err.println("os.version = " + osVersion);
    File currentArchive = jarFile;

    ZipFile zipFile = new ZipFile(currentArchive);
    Enumeration<ZipEntry> entries = (Enumeration<ZipEntry>) zipFile.entries();

    boolean nativeSWTFound = false;
    while (entries.hasMoreElements()) {
      ZipEntry entry = entries.nextElement();
      String pathname = entry.getName();
      if (((pathname.indexOf("linux") != -1) && (osName.indexOf("Linux") != -1))
          || ((pathname.indexOf("win32") != -1) && (osName.indexOf("Windows") != -1))) {
        if (pathname.endsWith("swt.jar")) {
          InputStream in = zipFile.getInputStream(entry);
          File outFile = new File(classPathFile, "swt.jar");
          FileOutputStream out = new FileOutputStream(outFile);
          byte[] buf = new byte[1024];
          while (true) {
            int nRead = in.read(buf, 0, buf.length);
            if (nRead <= 0)
              break;
            out.write(buf, 0, nRead);
          }
          in.close();
          out.close();
          nativeSWTFound = true;
        }
      }
    }

    if (! nativeSWTFound) {
      entries = (Enumeration<ZipEntry>) zipFile.entries();

      while (entries.hasMoreElements()) {
        ZipEntry entry = entries.nextElement();
        String pathname = entry.getName();
        if (pathname.endsWith("SWTSwing.jar")) {
          InputStream in = zipFile.getInputStream(entry);
          File outFile = new File(classPathFile, "swt.jar");
          FileOutputStream out = new FileOutputStream(outFile);
          byte[] buf = new byte[1024];
          while (true) {
            int nRead = in.read(buf, 0, buf.length);
            if (nRead <= 0)
              break;
            out.write(buf, 0, nRead);
          }
          in.close();
          out.close();
          nativeSWTFound = true;
        }
      }
    }
/*
    if (! nativeSWTFound) {
      entries = (Enumeration<ZipEntry>) zipFile.entries();

      while (entries.hasMoreElements()) {
        ZipEntry entry = entries.nextElement();
        String pathname = entry.getName();
        if (pathname.endsWith("rwt.jar")) {
          InputStream in = zipFile.getInputStream(entry);
          File outFile = new File(classPathFile, "swt.jar");
          FileOutputStream out = new FileOutputStream(outFile);
          byte[] buf = new byte[1024];
          while (true) {
            int nRead = in.read(buf, 0, buf.length);
            if (nRead <= 0)
              break;
            out.write(buf, 0, nRead);
          }
          in.close();
          out.close();
          nativeSWTFound = true;
        }
      }
    }
*/
    zipFile.close();
  }

  private static boolean isSWTAvailable() {
    try {
      Display.getDefault().dispose();
    } catch (NoClassDefFoundError noClassDefFoundError) {
      return false;
    } catch (UnsatisfiedLinkError unsatisfiedLinkError) {
      return false;
    }
    return true;
  }

  private void addFileToClassLoader(String string) throws IOException {
    File file = new File(string);
    addFileToClassLoader(file);
  }

  private void addFileToClassLoader(File file) throws IOException {
    addUrlToClassLoader(file.toURI().toURL());
  }

  private void addUrlToClassLoader(URL url) throws IOException {
    URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();

    try {
      Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
      method.setAccessible(true);
      method.invoke(systemClassLoader, new Object[] { url });
    } catch (Throwable throwable) {
      IOException ioException = new IOException("Error, could not add URL to system classloader", throwable);
      while (throwable != null) {
        throwable.printStackTrace();
        throwable = throwable.getCause();
      }
      throw ioException;
    }
  }

}
